/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.daydream.template.sequence.annotation;

import com.google.common.base.MoreObjects;
import com.google.common.collect.Maps;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.pajamas.daydream.template.sequence.SequenceTemplate;
import org.pajamas.daydream.template.sequence.context.MapContext;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/**
 * @author william
 * @since 2024/4/13
 */
@Getter
@Slf4j
public abstract class AbstractSequenceKeySupport {
    public static final String KEY_RUNTIME_CONTEXT_CLASS_NAME = "CLASS_NAME";
    public static final String KEY_RUNTIME_CONTEXT_METHOD_NAME = "METHOD_NAME";
    private final SequenceTemplate template;

    protected AbstractSequenceKeySupport(SequenceTemplate template) {
        this.template = template;
    }

    protected AbstractSequenceKeySupport() {
        this(null);
    }

    protected MapContext collectRuntimeContext(@Nonnull Method method, @Nullable Object[] args) {
        Map<String, Object> map = this.newRuntimeContextMap(method);
        if (ArrayUtils.isNotEmpty(args)) {

            Object[] parameterValues = MoreObjects.firstNonNull(args, new Object[0]);
            Annotation[][] parametersAnnotations = MoreObjects.firstNonNull(method.getParameterAnnotations(),
                    new Annotation[0][0]);
            if (parameterValues.length != parametersAnnotations.length) {
                throw new IllegalArgumentException("parameterValues.length != parameterAnnotations.length");
            }

            for (int idx = 0; idx < parameterValues.length; idx++) {
                Object value = parameterValues[idx];
                final int paramIdx = idx;
                Arrays.stream(MoreObjects.firstNonNull(parametersAnnotations[idx], new Annotation[0]))
                        .filter(Objects::nonNull)
                        .filter(annotation -> SequenceKey.class.equals(annotation.annotationType()))
                        .map(SequenceKey.class::cast)
                        .map(SequenceKey::key)
                        .map(StringUtils::trim)
                        .map(key -> StringUtils.firstNonBlank(key, String.valueOf(paramIdx)))
                        .forEach(key -> map.put(key, value));
            }

        }
        return new MapContext(map);
    }

    protected Optional<SequenceBinding> extractBinding(Object[] args) {
        Optional<SequenceBinding> bindingOptional = Arrays.stream(MoreObjects.firstNonNull(args, new Object[0]))
                .filter(Objects::nonNull)
                .filter(arg -> SequenceBinding.class.equals(arg.getClass()))
                .map(SequenceBinding.class::cast)
                .findFirst();
        if (!bindingOptional.isPresent()) {
            log.debug("extractBinding.bindingOptional is not present");
        }
        return bindingOptional;
    }

    @Nonnull
    private Map<String, Object> newRuntimeContextMap(@Nonnull Method method) {
        Map<String, Object> map = Maps.newHashMap();
        String className = Objects.requireNonNull(method).getDeclaringClass().getName();
        String methodName = method.getName();
        map.put(KEY_RUNTIME_CONTEXT_CLASS_NAME, className);
        map.put(KEY_RUNTIME_CONTEXT_METHOD_NAME, methodName);
        return map;
    }
}
